package com.cn.lp.export.server.base;


import com.cn.lp.export.server.base.context.CoreResponseCode;
import com.cn.lp.export.server.base.context.ExportExceptionCreator;
import com.cn.lp.export.server.base.context.IDCreator;

/**
 * Created by qirong on 2019/3/22.
 */
public class SnowFlake implements IDCreator<Long> {

    // 缩短时间间隔，比如每毫秒2万个ID, 可以缩短成每秒20万个ID

    // =============== 单位时间大小(毫秒) ===============

    private final static long TIMESTAMP_SIZE = 50L;

    // =============== 起始的时间戳(毫秒) = ==============

    private final static long START_TIMESTAMP = 1552914654779L / TIMESTAMP_SIZE;

    // =============== 每一部分占用的位数(合起来30位，其他的就是时间位) ===============

    private final static long SEQUENCE_BIT = 22;    // 序列号占用的位数
    private final static long SERVICE_BIT = 7;      // 服务号占用的位数
    private final static long CLUSTER_BIT = 1;      // 集群号占用的位数

    // =============== 每一部分的最大值 ===============

    private final static long MAX_CLUSTER_NUM = ~(-1L << CLUSTER_BIT);
    private final static long MAX_SERVICE_NUM = ~(-1L << SERVICE_BIT);
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    // =============== 每一部分向左的位移 ===============

    private final static long SERVICE_OFFSET = SEQUENCE_BIT;
    private final static long CLUSTER_OFFSET = SEQUENCE_BIT + SERVICE_BIT;
    private final static long TIMESTAMP_OFFSET = CLUSTER_OFFSET + CLUSTER_BIT;

    private long clusterNum;            // 集群号
    private long serviceNum;            // 服务号
    private long sequence = 0L;         // 序列号
    private long lastTimestamp = -1L;   // 上一次时间戳

    /**
     * @param clusterNum 集群号，0开始
     * @param serviceNum 服务号，0开始
     */
    public SnowFlake(long clusterNum, long serviceNum) {
        if (clusterNum > MAX_CLUSTER_NUM || clusterNum < 0) {
            throw new IllegalArgumentException("集群号不在[0," + MAX_CLUSTER_NUM + "]范围内.");
        }
        if (serviceNum > MAX_SERVICE_NUM || serviceNum < 0) {
            throw new IllegalArgumentException("服务号不在[0," + MAX_SERVICE_NUM + "]范围内.");
        }
        this.clusterNum = clusterNum;
        this.serviceNum = serviceNum;
    }

    /**
     * 产生下一个ID
     */
    public synchronized long newId() {
        long currTimestamp = getNewTimestamp();
        //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (currTimestamp < lastTimestamp) {
            throw ExportExceptionCreator.create(CoreResponseCode.UNKNOWN_ERROR.getCode(), "Clock moved backwards.  Refusing to generate id");
        }
        if (currTimestamp == lastTimestamp) {
            // 相同毫秒内，序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currTimestamp = getNextTime();
            }
        } else {
            // 不同毫秒内，序列号置为0
            sequence = 0L;
        }

        lastTimestamp = currTimestamp;

        return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET  // 时间戳部分
            | clusterNum << CLUSTER_OFFSET                            // 集群号部分
            | serviceNum << SERVICE_OFFSET                            // 服务号部分
            | sequence;                                             // 序列号部分
    }

    private long getNextTime() {
        long mill = getNewTimestamp();
        while (mill <= lastTimestamp) {
            mill = getNewTimestamp();
        }
        return mill;
    }

    private long getNewTimestamp() {
        return System.currentTimeMillis() / TIMESTAMP_SIZE;
    }

    @Override
    public Long createNext(Class<?> clazz) {
        return this.createNext(clazz.getName());
    }

    @Override
    public Long createNext(String key) {
        return newId();
    }

    public long getServiceNum(long id) {
        return (id & (((2 << SERVICE_BIT - 1) - 1) << SEQUENCE_BIT)) >> SEQUENCE_BIT;
    }

    public long getClusterNum(long id) {
        return (id & (((2 << CLUSTER_BIT - 1) - 1) << (SEQUENCE_BIT + SERVICE_BIT))) >> (SEQUENCE_BIT + SERVICE_BIT);
    }

//    public static void main(String[] args) {
//
//        long v = (~(-1L << (63 - SEQUENCE_BIT - SERVICE_BIT - CLUSTER_BIT))) * TIMESTAMP_SIZE;
//        Duration d = Duration.of(v, ChronoUnit.MILLIS);
//        System.out.println("时间量: " + (d.toDays() / 365) + " 年 ");
//        System.out.println("时间精度: " + TIMESTAMP_SIZE + " 毫秒");
//        System.out.println("自增数: " + (MAX_SEQUENCE + 1));
//        System.out.println("服务数: " + (MAX_SERVICE_NUM + 1));
//        System.out.println("集群数: " + (MAX_CLUSTER_NUM + 1));
//
//        long serviceNum = 6;
//        long clusterNum = 1;
//        SnowFlake snowFlake = new SnowFlake(clusterNum, serviceNum);
//        Long newID = snowFlake.newId();
//        System.out.println(newID);
//        System.out.println(String.valueOf(newID).length());
//        System.out.println("二进制显示 : " + Long.toBinaryString(newID));
//        System.out.println(Long.toBinaryString(serviceNum));
//        System.out.println(Long.toBinaryString(newID));
//
//        System.out.println(Long.toBinaryString(((2 << SERVICE_BIT - 1) - 1)));
//        System.out.println(Long.toBinaryString(((2 << SERVICE_BIT - 1) - 1) << SEQUENCE_BIT));
//        System.out.println(Long.toBinaryString(newID & (((2 << SERVICE_BIT - 1) - 1) << SEQUENCE_BIT)));
//        System.out.println(Long.toBinaryString((newID & (((2 << SERVICE_BIT - 1) - 1) << SEQUENCE_BIT)) >> SEQUENCE_BIT));
//        System.out.println((newID & (((2 << SERVICE_BIT - 1) - 1) << SEQUENCE_BIT)) >> SEQUENCE_BIT);
//
//        System.out.println(clusterNum);
//        System.out.println(Long.toBinaryString(((2 << CLUSTER_BIT - 1) - 1)));
//        System.out.println(Long.toBinaryString(((2 << CLUSTER_BIT - 1) - 1) << (SEQUENCE_BIT + SERVICE_BIT)));
//        System.out.println(Long.toBinaryString((newID & (((2 << CLUSTER_BIT - 1) - 1) << (SEQUENCE_BIT + SERVICE_BIT))) >> (SEQUENCE_BIT + SERVICE_BIT)));
//        System.out.println((newID & (((2 << CLUSTER_BIT - 1) - 1) << (SEQUENCE_BIT + SERVICE_BIT))) >> (SEQUENCE_BIT + SERVICE_BIT));
//        System.out.println("二进制显示 : " + Long.toBinaryString(SERVICE_BIT << SEQUENCE_BIT));
//        Set set = Sets.<Long>newHashSet();
//        long t = System.currentTimeMillis();
//        for (int i = 0; i < 50000; i++) {
//            long id = snowFlake.newId();
//            if (!set.add(id)) {
//                System.out.println("重复!! " + id);
//            }
//        }
//        System.out.println(System.currentTimeMillis() - t);
//        System.out.println(set.size());
//    }
}